#include "log_cleaner.h"
#include <unistd.h>
#include <pwd.h>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDebug>
#include <QDir>
#include <QFileInfo>

namespace junk_clean {

LogCleaner::LogCleaner(QObject *parent)
    : Cleaner(parent),
      home_log_path_(""),
      total_size_(0),
      junk_mark_(0)
{
    struct passwd *pw = getpwuid(getuid());
    if (pw) {
        if (pw->pw_dir) {
            home_log_path_ = QString("%1/.log").arg(pw->pw_dir);
        } else {
            qCritical() << "Log cleaner get home path fail.";
        }
    } else {
        qCritical() << "Log cleaner get pw info fail.";
    }

    QDBusConnection::systemBus().connect(JUNK_CLEAN_DBUS_NAME,
                                         JUNK_CLEAN_DBUS_PATH,
                                         JUNK_CLEAN_DBUS_INTERFACE,
                                         "ScanForJunk",
                                         this,
                                         SLOT(on_ScanForJunk(QString,quint64,QString,quint64)));
    QDBusConnection::systemBus().connect(JUNK_CLEAN_DBUS_NAME,
                                         JUNK_CLEAN_DBUS_PATH,
                                         JUNK_CLEAN_DBUS_INTERFACE,
                                         "ScanFinish",
                                         this,
                                         SLOT(on_ScanFinish(QString,quint64)));
    QDBusConnection::systemBus().connect(JUNK_CLEAN_DBUS_NAME,
                                         JUNK_CLEAN_DBUS_PATH,
                                         JUNK_CLEAN_DBUS_INTERFACE,
                                         "CleanForJunk",
                                         this,
                                         SLOT(on_CleanForJunk(QString,quint64)));
    QDBusConnection::systemBus().connect(JUNK_CLEAN_DBUS_NAME,
                                         JUNK_CLEAN_DBUS_PATH,
                                         JUNK_CLEAN_DBUS_INTERFACE,
                                         "CleanFinish",
                                         this,
                                         SLOT(on_CleanFinish(QString)));
}

QString LogCleaner::Mark() const {
    return "log_cleaner";
}

enum Type LogCleaner::Type() const {
    return Type::kSystemJunk;
}

enum Level LogCleaner::Level() const {
    return Level::kGeneral;
}

QString LogCleaner::Name() const {
    return tr("Log file");
}

QString LogCleaner::Description() const {
    return tr("Logs generated by software operation");
}

void LogCleaner::Scan() {
    log_junks_.clear();
    total_size_ = 0;
    junk_mark_ = 0;

    const auto message = QDBusMessage::createMethodCall(JUNK_CLEAN_DBUS_NAME,
                                                        JUNK_CLEAN_DBUS_PATH,
                                                        JUNK_CLEAN_DBUS_INTERFACE,
                                                        "ScanLog");
    const auto reply = QDBusConnection::systemBus().call(message);
    if (reply.type() == QDBusMessage::MessageType::ErrorMessage || reply.type() == QDBusMessage::MessageType::InvalidMessage) {
        ScanHomeLog();
    }
}

void LogCleaner::Clean(QList<quint64> junk_marks) {
    QList<quint64> unprocessed_junk_marks;

    for (const auto &junk_mark: junk_marks) {
        auto iter = log_junks_.find(junk_mark);
        if (iter == log_junks_.end()) {
            unprocessed_junk_marks.push_back(junk_mark);
            continue;
        }
        if (QFile::exists(iter.value())) {
            if (!QFile::remove(iter.value())) {
                qCritical() << "Log cleaner clean junk [" << iter.value() << "] fail.";
            }
        }
        log_junks_.erase(iter);
        Q_EMIT sig_CleanForJunk(Mark(), junk_mark);
    }

    if (unprocessed_junk_marks.isEmpty()) {
        Q_EMIT sig_CleanFinish(Mark());
        return;
    }

    // 剩余垃圾需要由 d-bus 侧来清理
    auto message = QDBusMessage::createMethodCall(JUNK_CLEAN_DBUS_NAME,
                                                        JUNK_CLEAN_DBUS_PATH,
                                                        JUNK_CLEAN_DBUS_INTERFACE,
                                                        "CleanLog");
    QVariant arg;
    arg.setValue(unprocessed_junk_marks);
    message.setArguments({arg});

    const auto reply = QDBusConnection::systemBus().call(message);
    if (reply.type() == QDBusMessage::MessageType::ErrorMessage || reply.type() == QDBusMessage::MessageType::InvalidMessage) {
        qCritical() << "Log cleaner call d-bus interface clean junk fail";
        Q_EMIT sig_CleanFinish(Mark());
    }
}

void LogCleaner::ScanHomeLog() {
    do {
        if (home_log_path_.isEmpty()) {
            qCritical() << "Log cleaner scan get scan path fail.";
            break;
        }
        RecursiveScanInDir(home_log_path_);
    } while (0);

    Q_EMIT sig_ScanFinish(Mark(), total_size_);
}

void LogCleaner::RecursiveScanInDir(const QString &dir_path) {
    const QDir dir(dir_path);
    if (!dir.exists()) {
        return;
    }

    auto entries = dir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
    for (const auto &entry: entries) {
        auto absolute_file_path = dir.absoluteFilePath(entry);
        QFileInfo file_info(absolute_file_path);
        if (file_info.isDir()) {
            RecursiveScanInDir(absolute_file_path);
        } else {
            JunkItem junk_item;
            junk_item.mark = ++junk_mark_;
            junk_item.path = absolute_file_path;
            junk_item.size = file_info.size();
            log_junks_.insert(junk_mark_, absolute_file_path);
            total_size_ += file_info.size();
            Q_EMIT sig_ScanForJunk(Mark(), junk_item);
        }
    }
}

void LogCleaner::on_ScanForJunk(QString cleaner_mark, quint64 junk_mark, QString path, quint64 size) {
    if (cleaner_mark != Mark()) {
        return;
    }

    junk_mark_ = junk_mark;

    JunkItem junk_item;
    junk_item.mark = junk_mark;
    junk_item.path = path;
    junk_item.size = size;
    Q_EMIT sig_ScanForJunk(Mark(), junk_item);
}

void LogCleaner::on_ScanFinish(QString cleaner_mark, quint64 total_size) {
    if (cleaner_mark != Mark()) {
        return;
    }

    total_size_ += total_size;
    ScanHomeLog();
}

void LogCleaner::on_CleanForJunk(QString cleaner_mark, quint64 junk_mark) {
    if (cleaner_mark != Mark()) {
        return;
    }
    Q_EMIT sig_CleanForJunk(Mark(), junk_mark);
}

void LogCleaner::on_CleanFinish(QString cleaner_mark) {
    if (cleaner_mark != Mark()) {
        return;
    }
    Q_EMIT sig_CleanFinish(Mark());
}

}
